/*
 * Copyright (c) 2008-2010 Redpill Linpro AS
 * All rights reserved.
 *
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Most codes are obtained from varnish project.
 */

#include "cfptest.h"
#include "miniobj.h"
#include "vct.h"
#include "vqueue.h"
#include "vsb.h"

#define	MAX_FILESIZE	(1024 * 1024)
#define	MAX_TOKENS	400
#define ONLY_CLIENT(hp, av)                                             \
do {                                                                    \
        if (hp->sfd >= 0)                                               \
                ctt_log(hp->vl, 0,                                      \
                    "\"%s\" only possible in client", av[0]);           \
} while (0)

struct ctt_cmds;
struct cttlog;

struct client {
        unsigned                magic;
#define CLIENT_MAGIC            0x6242397c
        char                    *name;
        struct cttlog           *vl;
        VTAILQ_ENTRY(client)    list;

        char                    *spec;

        char                    *connect;

        unsigned                repeat;

        pthread_t               tp;
};

struct compile {
        unsigned                magic;
#define COMPILE_MAGIC            0x42ac3b7c
        char                    *name;
        struct cttlog           *vl;
        VTAILQ_ENTRY(compile)    list;

	char			*filename;
        int                     fds[4];
        pid_t                   pid;
        pthread_t               tp;
	int			started;
};

#define	CMD_ARGS char * const *av, void *priv, const struct ctt_cmds *cmd, struct cttlog *vl
typedef int cmd_f(CMD_ARGS);

struct ctt_cmds {
        const char      *name;
        cmd_f           *cmd;
};

struct cttlog {
	unsigned	magic;
#define	CTTLOG_MAGIC	0x82731202
	const char	*id;
	struct vsb	*vsb;
	pthread_mutex_t	mtx;
};

#define MAX_HDR         200

struct http {
        unsigned                magic;
#define HTTP_MAGIC              0x2f02169c
        int                     fd;
        int                     sfd;
        int                     timeout;
        struct cttlog           *vl;

        struct vsb              *vsb;

        int                     nrxbuf;
        char                    *rxbuf;
        int                     prxbuf;
        char                    *body;
        char                    bodylen[20];

        char                    *req[MAX_HDR];
        char                    *resp[MAX_HDR];

        long                    readbytes;
        char                    readbytes_len[20];
        long                    writebytes;
        char                    writebytes_len[20];

        int                     isheadreq;
};

struct macro {
        VTAILQ_ENTRY(macro)     list;
        char                    *name;
        char                    *val;
};

struct sema {
        unsigned                magic;
#define SEMA_MAGIC              0x29b64317
        char                    *name;
        VTAILQ_ENTRY(sema)      list;
        pthread_mutex_t         mtx;
        pthread_cond_t          cond;

        unsigned                waiters;
        unsigned                expected;
};

struct server {
        unsigned                magic;
#define IMUGI_MAGIC             0x208cd8e3
        char                    *name;
        struct cttlog           *vl;
        struct cttlog           *vl1;
        VTAILQ_ENTRY(server)     list;

        struct vsb              *args;
        int                     fds[4];
        pid_t                   pid;
        const char              *accept;

        pthread_t               tp;

	int			started;
        int                     vcl_nbr;
        char                    *workdir;
	int			withso;
};

/* lightweight addrinfo */
struct vss_addr {
        int                      va_family;
        int                      va_socktype;
        int                      va_protocol;
        socklen_t                va_addrlen;
        union {
                struct sockaddr_storage  _storage;
                struct sockaddr          sa;
        } va_addr;
};

static cmd_f cmd_delay;
static cmd_f cmd_server;
static cmd_f cmd_client;
static cmd_f cmd_sema;
static cmd_f cmd_test;
static cmd_f cmd_shell;
static cmd_f cmd_random;
static cmd_f cmd_compile;
static char *	compile_filename(const char *str);

static VTAILQ_HEAD(, client)  clients = VTAILQ_HEAD_INITIALIZER(clients);
static VTAILQ_HEAD(,compile) compiles = VTAILQ_HEAD_INITIALIZER(compiles);
static VTAILQ_HEAD(,macro) macro_list = VTAILQ_HEAD_INITIALIZER(macro_list);
static VTAILQ_HEAD(, sema)	semas = VTAILQ_HEAD_INITIALIZER(semas);
static VTAILQ_HEAD(, server) serveres = VTAILQ_HEAD_INITIALIZER(serveres);

static struct vsb	*cttlog_full;
#define	CTT_SERVER_APACHE	0
#define	CTT_SERVER_LIGHTTPD	1
static int		ctt_server = CTT_SERVER_LIGHTTPD;
static int		ctt_timeout = 3;	/* 3 seconds */
static int		ctt_verbosity = 0;
static int		ctt_error;
static int		ctt_stop;
static pthread_t	ctt_thread;
static pthread_mutex_t	cttlog_mtx;
static pthread_mutex_t	macro_mtx;
static pthread_mutex_t	sema_mtx;
static char		*ctt_desc;
static char		*ctt_tmpdir;
static const char	*ctt_file;
static const char *lead[] = {
        "----",
        "#   ",
        "##  ",
        "### ",
        "####"
};

static struct ctt_cmds ctt_cmds[] = {
	{ "server",     cmd_server },
        { "client",     cmd_client },
	{ "compile",	cmd_compile },
        { "delay",      cmd_delay },
        { "test",       cmd_test },
        { "shell",      cmd_shell },
        { "sema",       cmd_sema },
        { "random",     cmd_random },
        { NULL, NULL }
};

static cmd_f cmd_http_timeout;
static cmd_f cmd_http_txreq;
static cmd_f cmd_http_rxresp;
static cmd_f cmd_http_expect;
static cmd_f cmd_http_send;
static cmd_f cmd_http_sendfile;

static struct ctt_cmds http_cmds[] = {
        { "timeout",    cmd_http_timeout },
        { "txreq",      cmd_http_txreq },
        { "rxresp",     cmd_http_rxresp },
        { "expect",     cmd_http_expect },
        { "send",       cmd_http_send },
        { "sendfile",   cmd_http_sendfile },
        { "delay",      cmd_delay },
        { "sema",       cmd_sema },
        { NULL,         NULL }
};

#define NLEAD (sizeof(lead)/sizeof(lead[0]))

static void
ctt_loginit(void)
{

	cttlog_full = vsb_newauto();
	AN(cttlog_full != NULL);
	AZ(pthread_mutex_init(&cttlog_mtx, NULL));
}

static struct cttlog *
ctt_logopen(const char *id)
{
	struct cttlog *cl;

	ALLOC_OBJ(cl, CTTLOG_MAGIC);
	AN(cl);
	cl->id = id;
	cl->vsb = vsb_newauto();
	AZ(pthread_mutex_init(&cl->mtx, NULL));
	return (cl);
}

static void
ctt_log(struct cttlog *vl, unsigned lvl, const char *fmt, ...)
{

        CHECK_OBJ_NOTNULL(vl, CTTLOG_MAGIC);
        AZ(pthread_mutex_lock(&vl->mtx));
        assert(lvl < NLEAD);
        vsb_clear(vl->vsb);
        vsb_printf(vl->vsb, "%s %-4s ", lead[lvl], vl->id);
        va_list ap;
        va_start(ap, fmt);
        (void)vsb_vprintf(vl->vsb, fmt, ap);
        va_end(ap);
        vsb_putc(vl->vsb, '\n');
        vsb_finish(vl->vsb);
        AZ(vsb_overflowed(vl->vsb));

        AZ(pthread_mutex_lock(&cttlog_mtx));
        vsb_cat(cttlog_full, vsb_data(vl->vsb));
        AZ(pthread_mutex_unlock(&cttlog_mtx));

        if (lvl >= 0 && lvl <= ctt_verbosity)
                (void)fputs(vsb_data(vl->vsb), stdout);
        vsb_clear(vl->vsb);
        AZ(pthread_mutex_unlock(&vl->mtx));
        if (lvl == 0) {
                ctt_error = 1;
                if (pthread_self() != ctt_thread)
                        pthread_exit(NULL);
        }
}

static void
ctt_dump(struct cttlog *vl, unsigned lvl, const char *pfx, const char *str)
{
        int nl = 1;

        CHECK_OBJ_NOTNULL(vl, CTTLOG_MAGIC);
        assert(lvl < NLEAD);
        vsb_clear(vl->vsb);
        if (pfx == NULL)
                pfx = "";
        if (str == NULL)
                vsb_printf(vl->vsb, "%s %-4s %s(null)\n",
                    lead[lvl], vl->id, pfx);
        else
                for(; *str != '\0'; str++) {
                        if (nl) {
                                vsb_printf(vl->vsb, "%s %-4s %s| ",
                                    lead[lvl], vl->id, pfx);
                                nl = 0;
                        }
                        if (*str == '\r')
                                vsb_printf(vl->vsb, "\\r");
                        else if (*str == '\t')
                                vsb_printf(vl->vsb, "\\t");
                        else if (*str == '\n') {
                                vsb_printf(vl->vsb, "\\n\n");
                                nl = 1;
                        } else if (*str < 0x20 || *str > 0x7e)
                                vsb_printf(vl->vsb, "\\x%02x", *str);
                        else
                                vsb_printf(vl->vsb, "%c", *str);
                }
        if (!nl)
                vsb_printf(vl->vsb, "\n");
        vsb_finish(vl->vsb);
        AZ(vsb_overflowed(vl->vsb));
        if (lvl <= ctt_verbosity)
                (void)fputs(vsb_data(vl->vsb), stdout);
        AZ(pthread_mutex_lock(&cttlog_mtx));
        vsb_cat(cttlog_full, vsb_data(vl->vsb));
        AZ(pthread_mutex_unlock(&cttlog_mtx));
        vsb_clear(vl->vsb);
        if (lvl == 0) {
                ctt_error = 1;
                if (pthread_self() != ctt_thread)
                        pthread_exit(NULL);
        }
}

static void
ctt_logclose(struct cttlog *vl)
{

        CHECK_OBJ_NOTNULL(vl, CTTLOG_MAGIC);
        vsb_delete(vl->vsb);
        AZ(pthread_mutex_destroy(&vl->mtx));
        FREE_OBJ(vl);
}

static void
init_macro(void)
{

        AZ(pthread_mutex_init(&macro_mtx, NULL));
}

static void
macro_def(struct cttlog *vl, const char *instance, const char *name,
    const char *fmt, ...)
{
        char buf[256];
        struct macro *m;
        va_list ap;

        if (instance != NULL) {
                assert (snprintf(buf, sizeof buf, "%s_%s", instance, name)
                    < sizeof buf);
                name = buf;
        }

        AZ(pthread_mutex_lock(&macro_mtx));
        VTAILQ_FOREACH(m, &macro_list, list)
                if (!strcmp(name, m->name))
                        break;
        if (m == NULL && fmt != NULL) {
                m = calloc(sizeof *m, 1);
                AN(m);
                REPLACE(m->name, name);
                VTAILQ_INSERT_TAIL(&macro_list, m, list);
        }
        if (fmt != NULL) {
                AN(m);
                va_start(ap, fmt);
                free(m->val);
                m->val = NULL;
                assert(vasprintf(&m->val, fmt, ap) >= 0);
                va_end(ap);
                AN(m->val);
                ctt_log(vl, 2, "macro def %s=%s", name, m->val);
        } else if (m != NULL) {
                ctt_log(vl, 2, "macro undef %s", name);
                VTAILQ_REMOVE(&macro_list, m, list);
                free(m->name);
                free(m->val);
                free(m);
        }
        AZ(pthread_mutex_unlock(&macro_mtx));
}

static char *
macro_get(const char *name)
{
        struct macro *m;

        char *retval = NULL;
        AZ(pthread_mutex_lock(&macro_mtx));
        VTAILQ_FOREACH(m, &macro_list, list)
                if (!strcmp(name, m->name))
                        break;
        if (m != NULL)
                retval = strdup(m->val);
        AZ(pthread_mutex_unlock(&macro_mtx));
        return (retval);
}

static struct vsb *
macro_expand(struct cttlog *vl, const char *name)
{
        struct vsb *vsb;
        char *p, *q, *v;

        (void)vl;

        vsb = vsb_newauto();
        AN(vsb);
        while (*name != '\0') {
                p = strstr(name, "${");
                if (p == NULL) {
                        vsb_cat(vsb, name);
                        break;
                }
                vsb_bcat(vsb, name, p - name);
                q = strchr(p, '}');
                if (q == NULL) {
                        vsb_cat(vsb, name);
                        break;
                }
                assert(p[0] == '$');
                assert(p[1] == '{');
                assert(q[0] == '}');
                p += 2;
                *q = '\0';
                v = macro_get(p);
                if (v == NULL) {
                        ctt_log(vl, 0, "undefined macro `%s'", p);
                        break;
                }
                vsb_cat(vsb, macro_get(p));
                name = q + 1;
        }
        vsb_finish(vsb);
        return (vsb);
}

static void
init_sema(void)
{

	AZ(pthread_mutex_init(&sema_mtx, NULL));
}

static double
TIM_mono(void)
{
        struct timespec ts;

        assert(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
        return (ts.tv_sec + 1e-9 * ts.tv_nsec);
}

static void
TIM_sleep(double t)
{

        if (t > 100.0)
                (void)sleep((int)round(t));
        else
                (void)usleep((int)round(t * 1e6));
}

static char *
read_file(const char *fn)
{
        char *buf;
        ssize_t sz = MAX_FILESIZE;
        ssize_t s;
        int fd;

        fd = open(fn, O_RDONLY);
        if (fd < 0)
                return (NULL);
        buf = malloc(sz);
        assert(buf != NULL);
        s = read(fd, buf, sz - 1);
        if (s <= 0)
                return (NULL);
        AZ(close (fd));
        assert(s < sz);         /* XXX: increase MAX_FILESIZE */
        buf[s] = '\0';
        buf = realloc(buf, s + 1);
        assert(buf != NULL);
        return (buf);
}

static int
BackSlash(const char *s, char *res)
{
        int r;
        char c;
        unsigned u;

        assert(*s == '\\');
        r = c = 0;
        switch(s[1]) {
        case 'n':
                c = '\n';
                r = 2;
                break;
        case 'r':
                c = '\r';
                r = 2;
                break;
        case 't':
                c = '\t';
                r = 2;
                break;
        case '"':
                c = '"';
                r = 2;
                break;
        case '\\':
                c = '\\';
                r = 2;
                break;
        case '0': case '1': case '2': case '3':
        case '4': case '5': case '6': case '7':
                for (r = 1; r < 4; r++) {
                        if (!isdigit(s[r]))
                                break;
                        if (s[r] - '0' > 7)
                                break;
                        c <<= 3;        /*lint !e701 signed left shift */
                        c |= s[r] - '0';
                }
                break;
        case 'x':
                if (1 == sscanf(s + 1, "x%02x", &u)) {
                        assert(!(u & ~0xff));
                        c = u;  /*lint !e734 loss of precision */
                        r = 4;
                }
                break;
        default:
                break;
        }
        if (res != NULL)
                *res = c;
        return (r);
}

static int
parse_string(char *buf, const struct ctt_cmds *cmd, void *priv, struct cttlog *vl)
{
        char *token_s[MAX_TOKENS], *token_e[MAX_TOKENS];
        struct vsb *token_exp[MAX_TOKENS];
        char *p, *q;
        int nest_brace;
        int rc, tn;
        const struct ctt_cmds *cp;

        assert(buf != NULL);
        rc = 0;
        for (p = buf; *p != '\0'; p++) {
                if (ctt_error || ctt_stop)
                        break;
                /* Start of line */
                if (isspace(*p))
                        continue;
                if (*p == '#') {
                        for (; *p != '\0' && *p != '\n'; p++)
                                ;
                        if (*p == '\0')
                                break;
                        continue;
                }

                /* First content on line, collect tokens */
                tn = 0;
                while (*p != '\0') {
                        assert(tn < MAX_TOKENS);
                        if (*p == '\n') { /* End on NL */
                                break;
                        }
                        if (isspace(*p)) { /* Inter-token whitespace */
                                p++;
                                continue;
                        }
                        if (*p == '\\' && p[1] == '\n') { /* line-cont */
                                p += 2;
                                continue;
                        }
                        if (*p == '"') { /* quotes */
                                token_s[tn] = ++p;
                                q = p;
                                for (; *p != '\0'; p++) {
                                        if (*p == '"')
                                                break;

                                        if (*p == '\\') {
                                                p += BackSlash(p, q) - 1;
                                                q++;
                                        } else {
                                                if (*p == '\n')
                                                        fprintf(stderr, "Unterminated quoted string in line:\n%s", p);
                                                assert(*p != '\n');
                                                *q++ = *p;
                                        }
                                }
                                token_e[tn++] = q;
                                p++;
                        } else if (*p == '{') { /* Braces */
                                nest_brace = 0;
                                token_s[tn] = p + 1;
                                for (; *p != '\0'; p++) {
                                        if (*p == '{')
                                                nest_brace++;
                                        else if (*p == '}') {
                                                if (--nest_brace == 0)
                                                        break;
                                        }
                                }
                                assert(*p == '}');
                                token_e[tn++] = p++;
                        } else { /* other tokens */
                                token_s[tn] = p;
                                for (; *p != '\0' && !isspace(*p); p++)
                                        ;
                                token_e[tn++] = p;
                        }
                }
                assert(tn < MAX_TOKENS);
                token_s[tn] = NULL;
                for (tn = 0; token_s[tn] != NULL; tn++) {
                        token_exp[tn] = NULL;
                        AN(token_e[tn]);        /*lint !e771 */
                        *token_e[tn] = '\0';    /*lint !e771 */
                        if (NULL == strstr(token_s[tn], "${"))
                                continue;
                        token_exp[tn] = macro_expand(vl, token_s[tn]);
                        token_s[tn] = vsb_data(token_exp[tn]);
                        token_e[tn] = strchr(token_s[tn], '\0');
                }

                for (cp = cmd; cp->name != NULL; cp++)
                        if (!strcmp(token_s[0], cp->name))
                                break;
                if (cp->name == NULL)
                        ctt_log(vl, 0, "Unknown command: \"%s\"", token_s[0]);

                assert(cp->cmd != NULL);
                rc = cp->cmd(token_s, priv, cmd, vl);
                if (rc < 0)
                        break;
        }
        return (rc);
}

static void
reset_cmds(const struct ctt_cmds *cmd)
{

        for (; cmd->name != NULL; cmd++)
                cmd->cmd(NULL, NULL, NULL, NULL);
}

static void
ctt_logreset(void)
{

        vsb_clear(cttlog_full);
}

static const char *
ctt_logfull(void)
{

        vsb_finish(cttlog_full);
        AZ(vsb_overflowed(cttlog_full));
        return (vsb_data(cttlog_full));
}

static double
exec_file(const char *fn, int s, int e, struct cttlog *vl)
{
        char *buf;
        double t0;
        unsigned old_err;
        int rc;

        t0 = TIM_mono();
        ctt_stop = 0;
        ctt_file = fn;
        ctt_desc = NULL;
        ctt_log(vl, 1, "TEST [%d of %d] %s starting", s, e, fn);
        buf = read_file(fn);
        if (buf == NULL)
                ctt_log(vl, 0, "Cannot read file '%s': %s",
                    fn, strerror(errno));
        rc = parse_string(buf, ctt_cmds, NULL, vl);
        old_err = ctt_error;
        ctt_stop = 1;
        ctt_log(vl, 1, "RESETTING after %s", fn);
        reset_cmds(ctt_cmds);
        ctt_error = old_err;

        if (rc < 0) {
                        ctt_log(vl, 1, "TEST [%d of %d] %s skipped", s, e, fn);
                        ctt_logreset();
        } else {
                if (ctt_error)
                        ctt_log(vl, 1, "TEST [%d of %d] %s FAILED", s, e, fn);
                else {
                        ctt_log(vl, 1, "TEST [%d of %d] %s completed", s, e,
                            fn);
                        ctt_logreset();
                }
        }

        t0 = TIM_mono() - t0;

        if (ctt_error && ctt_verbosity == 0)
                printf("%s", ctt_logfull());
        else if (ctt_verbosity == 0) {
                if (rc < 0)
                        printf("#    top  TEST [%d of %d] %s skipped (%.3fs)\n",
                            s, e, fn, t0);
                else
                        printf("#    top  TEST [%d of %d] %s passed (%.3fs)\n",
                            s, e, fn, t0);
        }

        ctt_file = NULL;
        free(ctt_desc);
        return (t0);
}

static struct server *
server_new(const char *name)
{
        struct server *v;
        char buf[BUFSIZ];

        AN(name);
        ALLOC_OBJ(v, IMUGI_MAGIC);
        AN(v);
        REPLACE(v->name, name);

        bprintf(buf, "%s/%s", ctt_tmpdir, name);
        v->workdir = strdup(buf);
        AN(v->workdir);
        bprintf(buf, "rm -rf %s ; mkdir -p %s", v->workdir, v->workdir);
        AZ(system(buf));

        v->vl = ctt_logopen(name);
        AN(v->vl);

        v->vl1 = ctt_logopen(name);
        AN(v->vl1);

        if (*v->name != 's')
                ctt_log(v->vl, 0, "IMUGI name must start with 's'");

        v->args = vsb_newauto();
        v->accept = "127.0.0.1:0";
	v->started = 0;
        VTAILQ_INSERT_TAIL(&serveres, v, list);

        return (v);
}

static void
server_delete(struct server *v)
{

        CHECK_OBJ_NOTNULL(v, IMUGI_MAGIC);
        ctt_logclose(v->vl);
        free(v->name);
        free(v->workdir);

        /*
         * We do not delete the workdir, it may contain stuff people
         * want (coredumps, shmlog/stats etc), and trying to divine
         * "may want" is just too much trouble.  Leave it around and
         * nuke it at the start of the next test-run.
         */

        /* XXX: MEMLEAK (?) */
        FREE_OBJ(v);
}

static void
TCP_nonblocking(int sock)
{
        int i;

        i = 1;
        AZ(ioctl(sock, FIONBIO, &i));
}

static void *
server_thread(void *priv)
{
        struct server *v;
        struct pollfd *fds, fd;
        char buf[BUFSIZ];
        int i;

        CAST_OBJ_NOTNULL(v, priv, IMUGI_MAGIC);
        TCP_nonblocking(v->fds[0]);
        while (1) {
                fds = &fd;
                memset(fds, 0, sizeof fds);
                fds->fd = v->fds[0];
                fds->events = POLLIN;
                i = poll(fds, 1, 1000);
                if (i == 0)
                        continue;
                if (fds->revents & (POLLERR|POLLHUP))
                        break;
                i = read(v->fds[0], buf, sizeof(buf) - 1);
                if (i <= 0)
                        break;
                buf[i] = '\0';
                ctt_dump(v->vl1, 3, "debug", buf);
        }
        return (NULL);
}

static void
server_lighttpd_conf(struct server *v)
{
	struct compile *c, *c2;
	struct vsb *vsb, *vsbnew;
	char *temp;
	char fn[BUFSIZ];
        ssize_t s;
        int fd;

	temp = read_file("./tmpl/lighttpd.conf");
	if (temp == NULL) {
		ctt_log(v->vl, 0, "failed to read lighttpd.conf");
		return;
	}
	vsb = macro_expand(v->vl, temp);
	free(temp);

	vsbnew = vsb_newauto();
	assert(vsbnew != NULL);
	vsb_cat(vsbnew, vsb_data(vsb));

	if (v->withso == 1) {
		VTAILQ_FOREACH_SAFE(c, &compiles, list, c2) {
			sprintf(fn, 
			    "\ncfp.module = \"%s/build/%s.so\"\n\n",
			    macro_get("tmpdir"),
			    compile_filename(c->filename));
			vsb_cat(vsbnew, fn);
		}
	}
	vsb_finish(vsbnew);

	sprintf(fn, "%s/lighttpd.conf", v->workdir);
	fd = open(fn, O_CREAT | O_WRONLY);
        assert(fd >= 0);
        s = write(fd, vsb_data(vsbnew), vsb_len(vsbnew));
	assert(s == vsb_len(vsbnew));
        AZ(close (fd));
	vsb_delete(vsb);
	vsb_delete(vsbnew);
}

static void
server_apache_conf(struct server *v)
{
	struct compile *c, *c2;
	struct vsb *vsb, *vsbnew;
	char *temp;
	char fn[BUFSIZ];
        ssize_t s;
        int fd;


	temp = read_file("./tmpl/httpd.conf");
	if (temp == NULL) {
		ctt_log(v->vl, 0, "failed to read httpd.conf");
		return;
	}
	vsb = macro_expand(v->vl, temp);
	free(temp);

	vsbnew = vsb_newauto();
	assert(vsbnew != NULL);
	vsb_cat(vsbnew, vsb_data(vsb));

	if (v->withso == 1) {
		VTAILQ_FOREACH_SAFE(c, &compiles, list, c2) {
			sprintf(fn, 
			    "\nCFPModule \"%s/build/%s.so\"\n\n",
			    macro_get("tmpdir"),
			    compile_filename(c->filename));
			vsb_cat(vsbnew, fn);
		}
	}
	vsb_finish(vsbnew);

	sprintf(fn, "%s/httpd.conf", v->workdir);
	fd = open(fn, O_CREAT | O_WRONLY);
        assert(fd >= 0);
        s = write(fd, vsb_data(vsbnew), vsb_len(vsbnew));
	assert(s == vsb_len(vsbnew));
        AZ(close (fd));
	vsb_delete(vsb);
	vsb_delete(vsbnew);

	/* creates a minimal directory and file to launch */
	bprintf(fn, "%s/logs", macro_get("tmpdir"));
	AZ(mkdir(fn, 0755));
	bprintf(fn, "%s/conf", macro_get("tmpdir"));
	AZ(mkdir(fn, 0755));
	bprintf(fn, "%s/conf/mime.types", macro_get("tmpdir"));
	fd = open(fn, O_CREAT | O_WRONLY, 0644);
	assert(fd >= 0);
	AZ(close (fd));
}

static void
server_launch(struct server *v)
{
        struct vsb *vsb;
        int i;

	if (ctt_server == CTT_SERVER_LIGHTTPD)
		server_lighttpd_conf(v);
	else
		server_apache_conf(v);

        vsb_finish(v->args);
        AZ(vsb_overflowed(v->args));
        ctt_log(v->vl, 2, "Launch");
        vsb = vsb_newauto();
        AN(vsb);
	if (ctt_server == CTT_SERVER_LIGHTTPD) {
		vsb_printf(vsb, " cd ../lighttpd-1.4.26/src &&");
		vsb_printf(vsb, " ./lighttpd -D -f %s/lighttpd.conf",
		    v->workdir);
		vsb_printf(vsb, " -m ./.libs");
	} else {
		vsb_printf(vsb, " cd ../httpd-2.2.15/ &&");
		vsb_printf(vsb, " ./httpd -d . -f %s/httpd.conf",
		    v->workdir);
	}
        vsb_printf(vsb, " %s", vsb_data(v->args));
        vsb_finish(vsb);
        AZ(vsb_overflowed(vsb));
        ctt_log(v->vl, 3, "CMD: %s", vsb_data(vsb));
        AZ(pipe(&v->fds[0]));
        AZ(pipe(&v->fds[2]));
        v->pid = fork();
        assert(v->pid >= 0);
        if (v->pid == 0) {
                assert(dup2(v->fds[0], 0) == 0);
                assert(dup2(v->fds[3], 1) == 1);
                assert(dup2(1, 2) == 2);
                AZ(close(v->fds[0]));
                AZ(close(v->fds[1]));
                AZ(close(v->fds[2]));
                AZ(close(v->fds[3]));
                for (i = 3; i <getdtablesize(); i++)
                        close(i);
                AZ(execl("/bin/sh", "/bin/sh", "-c", vsb_data(vsb), NULL));
                exit(1);
        }
        AZ(close(v->fds[0]));
        AZ(close(v->fds[3]));
        v->fds[0] = v->fds[2];
        v->fds[2] = v->fds[3] = -1;
        vsb_delete(vsb);
        AZ(pthread_create(&v->tp, NULL, server_thread, v));
	v->started = 1;

	macro_def(v->vl, v->name, "sock", "127.0.0.1:55544");
}

static void
server_stop(struct server *v)
{

        if (v->started == 0)
                server_launch(v);
        if (ctt_error)
                return;
        macro_def(v->vl, v->name, "sock", NULL);
        ctt_log(v->vl, 2, "Stop");
}

static void
server_wait(struct server *v)
{
        void *p;
	char cmd[BUFSIZ], *pid;
        int status, r;

        if (v->started == 0)
                return;
        if (ctt_error)
                sleep(1);
        server_stop(v);
        ctt_log(v->vl, 2, "Wait");
        v->started = 0;

	if (ctt_server == CTT_SERVER_LIGHTTPD)
		bprintf(cmd, "%s/lighttpd.pid", macro_get("tmpdir"));
	else
		bprintf(cmd, "%s/httpd.pid", macro_get("tmpdir"));
	pid = read_file(cmd);
	assert(pid != NULL);
	bprintf(cmd, "kill %s", pid);
	free(pid);
	system(cmd);

        AZ(close(v->fds[1]));

        AZ(pthread_join(v->tp, &p));
        AZ(close(v->fds[0]));
        status = -1;
        r = wait4(v->pid, &status, 0, NULL);
        ctt_log(v->vl, 2, "R %d Status: %04x", r, status);
        if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
                return;
        ctt_log(v->vl, 0, "Bad exit code: %04x sig %x exit %x core %x",
            status, WTERMSIG(status), WEXITSTATUS(status),
            WCOREDUMP(status));
}

static void
server_start(struct server *v)
{

        if (v->started == 0)
                server_launch(v);
        ctt_log(v->vl, 2, "Start");
}

static int
cmd_server(CMD_ARGS)
{
        struct server *v, *v2;

        (void)priv;
        (void)cmd;
        (void)vl;

        if (av == NULL) {
                /* Reset and free */
                VTAILQ_FOREACH_SAFE(v, &serveres, list, v2) {
			if (v->started == 1)
				server_wait(v);
                        VTAILQ_REMOVE(&serveres, v, list);
                        server_delete(v);
                }
                return (0);
        }

        assert(!strcmp(av[0], "server"));
        av++;

        VTAILQ_FOREACH(v, &serveres, list)
                if (!strcmp(v->name, av[0]))
                        break;
        if (v == NULL)
                v = server_new(av[0]);
        av++;

        for (; *av != NULL; av++) {
                if (ctt_error)
                        break;
                if (!strcmp(*av, "-accept")) {
                        AN(av[1]);
                        v->accept = av[1];
                        av++;
                        continue;
                }
                if (!strcmp(*av, "-arg")) {
                        AN(av[1]);
                        vsb_cat(v->args, " ");
                        vsb_cat(v->args, av[1]);
                        av++;
                        continue;
                }
                if (!strcmp(*av, "-launch")) {
                        server_launch(v);
                        continue;
                }
		if (!strcmp(*av, "-start")) {
                        server_start(v);
                        continue;
                }
		if (!strcmp(*av, "-wait")) {
			server_wait(v);
                        continue;
                }
		if (!strcmp(*av, "-withso")) {
                        v->withso = 1;
                        continue;
                }
                ctt_log(v->vl, 0, "Unknown server argument: %s", *av);
        }
        return (0);
}

/*
 * Take a string provided by the user and break it up into address and
 * port parts.  Examples of acceptable input include:
 *
 * "localhost" - "localhost:80" - "localhost:80@1"
 * "127.0.0.1" - "127.0.0.1:80" - "127.0.0.1:80@1"
 * "0.0.0.0" - "0.0.0.0:80" - "0.0.0.0:80@1"
 * "[::1]" - "[::1]:80" - "[::1]:80@1"
 * "[::]" - "[::]:80" - "[::]:80@1"
 */
static int
VSS_parse(const char *str, char **addr, char **port, char **profile)
{
        const char *o, *p;

        *addr = *port = *profile = NULL;

        if (str[0] == '[') {
                /* IPv6 address of the form [::1]:80@1 */
                if ((p = strchr(str, ']')) == NULL ||
                    p == str + 1 ||
                    (p[1] != '\0' && p[1] != ':' && p[1] != '@'))
                        return (-1);
                *addr = strndup(str + 1, p - (str + 1));
                AN(*addr);
                if (p[1] == ':') {
                        if ((o = strchr(p + 2, '@')) == NULL) {
                                *port = strdup(p + 2);
                                AN(*port);
                        } else {
                                *port = strndup(p + 2, o - (p + 3));
                                AN(*port);
                                *profile = strdup(o + 1);
                                AN(*profile);
                        }
                } else if (p[1] == ',') {
                        *profile = strdup(p + 2);
                        AN(*profile);
                }
        } else {
                /* IPv4 address of the form 127.0.0.1:80@1, or non-numeric */
                o = strchr(str, '@');
                p = strchr(str, ':');
                if (p == NULL) {
                        if (o == NULL) {
                                *addr = strdup(str);
                                AN(*addr);
                        } else {
                                *addr = strndup(str, o - str);
                                AN(*addr);
                                *profile = strdup(o + 1);
                                AN(*profile);
                        }
                } else {
                        if (p > str) {
                                *addr = strndup(str, p - str);
                                AN(*addr);
                        }
                        if (o == NULL) {
                                *port = strdup(p + 1);
                                AN(*port);
                        } else {
                                *port = strndup(p + 1, o - (p + 1));
                                AN(*port);
                                *profile = strdup(o + 1);
                                AN(*profile);
                        }
                }
        }
        return (0);
}

/*
 * For a given host and port, return a list of struct vss_addr, which
 * contains all the information necessary to open and bind a socket.  One
 * vss_addr is returned for each distinct address returned by
 * getaddrinfo().
 *
 * The value pointed to by the tap parameter receives a pointer to an
 * array of pointers to struct vss_addr.  The caller is responsible for
 * freeing each individual struct vss_addr as well as the array.
 *
 * The return value is the number of addresses resoved, or zero.
 *
 * XXX: We need a function to free the allocated addresses.
 */
static int
VSS_resolve(const char *addr, const char *port, struct vss_addr ***vap)
{
        struct addrinfo hints, *res0, *res;
        struct vss_addr **va;
        int i, ret;

        memset(&hints, 0, sizeof(hints));
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE;
        ret = getaddrinfo(addr, port, &hints, &res0);
        if (ret != 0) {
                fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(ret));
                return (0);
        }
        AN(res0);
        for (res = res0, i = 0; res != NULL; res = res->ai_next, ++i)
                /* nothing */ ;
        if (i == 0) {
                freeaddrinfo(res0);
                return (0);
        }
        va = calloc(i, sizeof(*va));
        AN(va);
        *vap = va;
        for (res = res0, i = 0; res != NULL; res = res->ai_next, ++i) {
                va[i] = calloc(1, sizeof(*va[i]));
                AN(va[i]);
                va[i]->va_family = res->ai_family;
                va[i]->va_socktype = res->ai_socktype;
                va[i]->va_protocol = res->ai_protocol;
                va[i]->va_addrlen = res->ai_addrlen;
                assert(va[i]->va_addrlen <= sizeof(va[i]->va_addr));
                memcpy(&va[i]->va_addr, res->ai_addr, va[i]->va_addrlen);
        }
        freeaddrinfo(res0);
        return (i);
}

/*
 * Connect to the socket specified by the address info in va.
 * Return the socket.
 */
static int
VSS_connect(const struct vss_addr *va)
{
        int sd;

        sd = socket(va->va_family, va->va_socktype, va->va_protocol);
        if (sd < 0) {
                if (errno != EPROTONOSUPPORT)
                        perror("socket()");
                return (-1);
        }
        if (connect(sd, &va->va_addr.sa, va->va_addrlen) != 0) {
                perror("connect()");
                (void)close(sd);
                return (-1);
        }
        return (sd);
}

/*
 * And the totally brutal version: Give me connection to this address
 */

static int
VSS_open(const char *str)
{
        int retval;
        char *addr = NULL, *port = NULL, *profile = NULL;
        int nvaddr, n;
        struct vss_addr **vaddr;

        retval = VSS_parse(str, &addr, &port, &profile);
        if (retval < 0)
                return (retval);
        nvaddr = VSS_resolve(addr, port, &vaddr);
        if (nvaddr <= 0) {
                free(addr);
                free(port);
                free(profile);
                return (-1);
        }
        for (n = 0; n < nvaddr; n++) {
                retval = VSS_connect(vaddr[n]);
                if (retval >= 0)
                        break;
        }
        for (n = 0; n < nvaddr; n++)
                free(vaddr[n]);
        free(vaddr);
        free(addr);
        free(port);
        free(profile);
        return (retval);
}

static struct client *
client_new(const char *name)
{
        struct client *c;

        AN(name);
        ALLOC_OBJ(c, CLIENT_MAGIC);
        AN(c);
        REPLACE(c->name, name);
        c->vl = ctt_logopen(name);
        AN(c->vl);
        if (*c->name != 'c')
                ctt_log(c->vl, 0, "Client name must start with 'c'");

        REPLACE(c->connect, "${s1_sock}");
        VTAILQ_INSERT_TAIL(&clients, c, list);
        return (c);
}

static void
client_delete(struct client *c)
{

        CHECK_OBJ_NOTNULL(c, CLIENT_MAGIC);
        ctt_logclose(c->vl);
        free(c->spec);
        free(c->name);
        free(c->connect);
        /* xxx: memleak (?)*/
        FREE_OBJ(c);
}

static int
cmd_http_timeout(CMD_ARGS)
{
        struct http *hp;

        (void)cmd;
        (void)vl;
        CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
        AN(av[1]);
        AZ(av[2]);
        hp->timeout = strtod(av[1], NULL) * 1000.0;
        return (0);
}

static char *
synth_body(const char *len)
{
        int i, j, k, l;
        char *b;

        AN(len);
        i = strtoul(len, NULL, 0);
        assert(i > 0);
        b = malloc(i + 1);
        AN(b);
        l = k = '!';
        for (j = 0; j < i; j++) {
                if ((j % 64) == 63) {
                        b[j] = '\n';
                        k++;
                        if (k == '~')
                                k = '!';
                        l = k;
                } else {
                        b[j] = l++;
                        if (l == '~')
                                l = '!';
                }
        }
        b[i - 1] = '\n';
        b[i] = '\0';
        return (b);
}

static void
http_write(struct http *hp, int lvl, const char *pfx, int hclose)
{
        int l;

        vsb_finish(hp->vsb);
        AZ(vsb_overflowed(hp->vsb));
        ctt_dump(hp->vl, lvl, pfx, vsb_data(hp->vsb));
	l = write(hp->fd, vsb_data(hp->vsb), vsb_len(hp->vsb));
	hp->writebytes += l;
        if (l != vsb_len(hp->vsb))
		ctt_log(hp->vl, 0, "Write failed: %s", strerror(errno));
        if (hclose > 0) {
                if (shutdown(hp->fd, SHUT_WR) != 0)
                        ctt_log(hp->vl, 0, "shutdown(2) failed:  %s",
                            strerror(errno));
        }
}

static const char *nl = "\r\n";

static int
cmd_http_txreq(CMD_ARGS)
{
        struct http *hp;
        const char *req = "GET";
        const char *url = "/";
        const char *proto = "HTTP/1.1";
        const char *body = NULL;
        int hc;

        (void)cmd;
        (void)vl;
        CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
        hc = 0;
        ONLY_CLIENT(hp, av);
        assert(!strcmp(av[0], "txreq"));
        av++;

        vsb_clear(hp->vsb);

        for(; *av != NULL; av++) {
                if (!strcmp(*av, "-url")) {
                        url = av[1];
                        av++;
                } else if (!strcmp(*av, "-proto")) {
                        proto = av[1];
                        av++;
                } else if (!strcmp(*av, "-req")) {
                        req = av[1];
                        if (!strcmp(req, "HEAD"))
                                hp->isheadreq = 1;
                        av++;
                } else
                        break;
        }
        vsb_printf(hp->vsb, "%s %s %s%s", req, url, proto, nl);
        for(; *av != NULL; av++) {
                if (!strcmp(*av, "-hdr")) {
                        vsb_printf(hp->vsb, "%s%s", av[1], nl);
                        av++;
                } else
                        break;
        }
        for(; *av != NULL; av++) {
                if (!strcmp(*av, "-body")) {
                        AZ(body);
                        body = av[1];
                        av++;
                } else if (!strcmp(*av, "-bodylen")) {
                        AZ(body);
                        body = synth_body(av[1]);
                        av++;
                } else if (!strcmp(*av, "-close")) {
                        hc++;
                        av++;
                } else
                        break;
        }
        if (*av != NULL)
                ctt_log(hp->vl, 0, "Unknown http txreq spec: %s\n", *av);
        if (body != NULL)
                vsb_printf(hp->vsb, "Content-Length: %zd%s", strlen(body), nl);
        vsb_cat(hp->vsb, nl);
        if (body != NULL) {
                vsb_cat(hp->vsb, body);
                vsb_cat(hp->vsb, nl);
        }
        http_write(hp, 4, "txreq", hc);
        return (0);
}

static int
http_rxchar_eof(struct http *hp, int n)
{
        int i;
        struct pollfd pfd[1];

        while (n > 0) {
                pfd[0].fd = hp->fd;
                pfd[0].events = POLLIN;
                pfd[0].revents = 0;
                i = poll(pfd, 1, hp->timeout);
                if (i <= 0)
                        ctt_log(hp->vl, 0, "HTTP rx failed (poll: %s)",
                            strerror(errno));
                assert(i > 0);
                assert(hp->prxbuf + n < hp->nrxbuf);
		i = read(hp->fd, hp->rxbuf + hp->prxbuf, n);
		hp->readbytes += i;
                if (i == 0)
                        return (i);
                if (i < 0)
			ctt_log(hp->vl, 0, "HTTP rx failed (read: %s)",
			    strerror(errno));
                hp->prxbuf += i;
                hp->rxbuf[hp->prxbuf] = '\0';
                n -= i;
        }
        return (1);
}

static void
http_rxchar(struct http *hp, int n)
{
        int i;

        i = http_rxchar_eof(hp, n);
        if (i <= 0)
                ctt_log(hp->vl, 0, "HTTP rx failed (%d %s)", i,
                    strerror(errno));
        assert(i > 0);
}

static void
http_rxhdr(struct http *hp)
{
        int i;
        char *p;

        CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
        hp->prxbuf = 0;
        hp->body = NULL;
        while (1) {
                http_rxchar(hp, 1);
                p = hp->rxbuf + hp->prxbuf - 1;
                i = 0;
                for (i = 0; p > hp->rxbuf; p--) {
                        if (*p != '\n')
                                break;
                        if (p - 1 > hp->rxbuf && p[-1] == '\r')
                                p--;
                        if (++i == 2)
                                break;
                }
                if (i == 2)
                        break;
        }
        ctt_dump(hp->vl, 4, "rxhdr", hp->rxbuf);
}

static void
http_splitheader(struct http *hp, int req)
{
        char *p, *q, **hh;
        int n;
        char buf[20];

        CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
        if (req) {
                memset(hp->req, 0, sizeof(hp->req));
                hh = hp->req;
        } else {
                memset(hp->resp, 0, sizeof(hp->resp));
                hh = hp->resp;
        }

        n = 0;
        p = hp->rxbuf;

        /* REQ/PROTO */
        while (vct_islws(*p))
                p++;
        hh[n++] = p;
        while (!vct_islws(*p))
                p++;
        assert(!vct_iscrlf(*p));
        *p++ = '\0';

        /* URL/STATUS */
        while (vct_issp(*p))            /* XXX: H space only */
                p++;
        assert(!vct_iscrlf(*p));
        hh[n++] = p;
        while (!vct_islws(*p))
                p++;
        if (vct_iscrlf(*p)) {
                hh[n++] = NULL;
                q = p;
                p += vct_skipcrlf(p);
                *q = '\0';
        } else {
                *p++ = '\0';
                /* PROTO/MSG */
                while (vct_issp(*p))            /* XXX: H space only */
                        p++;
                hh[n++] = p;
                while (!vct_iscrlf(*p))
                        p++;
                q = p;
                p += vct_skipcrlf(p);
                *q = '\0';
        }
        assert(n == 3);

        while (*p != '\0') {
                assert(n < MAX_HDR);
                if (vct_iscrlf(*p))
                        break;
                hh[n++] = p++;
                while (*p != '\0' && !vct_iscrlf(*p))
                        p++;
                q = p;
                p += vct_skipcrlf(p);
                *q = '\0';
        }
        p += vct_skipcrlf(p);
        assert(*p == '\0');

        for (n = 0; n < 3 || hh[n] != NULL; n++) {
                snprintf(buf, sizeof(buf), "http[%2d] ", n);
                ctt_dump(hp->vl, 4, buf, hh[n]);
        }
}

static char *
http_find_header(char * const *hh, const char *hdr)
{
        int n, l;
        char *r;

        l = strlen(hdr);

        for (n = 3; hh[n] != NULL; n++) {
                if (strncasecmp(hdr, hh[n], l) || hh[n][l] != ':')
                        continue;
                for (r = hh[n] + l + 1; vct_issp(*r); r++)
                        continue;
                return (r);
        }
        return (NULL);
}

static void
http_swallow_body(struct http *hp, char * const *hh, int body)
{
        char *p, *q;
        int i, l, ll;

        ll = 0;
        p = http_find_header(hh, "content-length");
        if (p != NULL && hp->isheadreq == 0) {
                l = strtoul(p, NULL, 0);
                hp->body = q = hp->rxbuf + hp->prxbuf;
                http_rxchar(hp, l);
                ctt_dump(hp->vl, 4, "body", hp->body);
                snprintf(hp->bodylen, sizeof(hp->bodylen), "%d", l);
                return;
        }
        p = http_find_header(hh, "transfer-encoding");
        if (p != NULL && !strcmp(p, "chunked")) {
                hp->body = hp->rxbuf + hp->prxbuf;
                while (1) {
                        l = hp->prxbuf;
                        do
                                http_rxchar(hp, 1);
                        while (hp->rxbuf[hp->prxbuf - 1] != '\n');
                        ctt_dump(hp->vl, 4, "len", hp->rxbuf + l);
                        i = strtoul(hp->rxbuf + l, &q, 16);
                        assert(q != hp->rxbuf + l);
                        assert(*q == '\0' || vct_islws(*q));
                        hp->prxbuf = l;
                        if (i > 0) {
                                ll += i;
                                http_rxchar(hp, i);
                                ctt_dump(hp->vl, 4, "chunk", hp->rxbuf + l);
                        }
                        l = hp->prxbuf;
                        http_rxchar(hp, 2);
                        assert(vct_iscrlf(hp->rxbuf[l]));
                        assert(vct_iscrlf(hp->rxbuf[l + 1]));
                        hp->prxbuf = l;
                        hp->rxbuf[l] = '\0';
                        if (i == 0)
                                break;
                }
                ctt_dump(hp->vl, 4, "body", hp->body);
                snprintf(hp->bodylen, sizeof(hp->bodylen), "%d", ll);
                return;
        }
        if (body) {
                hp->body = q = hp->rxbuf + hp->prxbuf;
                do  {
                        i = http_rxchar_eof(hp, 1);
                        ll += i;
                } while (i > 0);
                ctt_dump(hp->vl, 4, "rxeof", hp->body);
        }
        snprintf(hp->bodylen, sizeof(hp->bodylen), "%d", ll);
}

static int
cmd_http_rxresp(CMD_ARGS)
{
        struct http *hp;

        (void)cmd;
        (void)vl;
        CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
        ONLY_CLIENT(hp, av);
        assert(!strcmp(av[0], "rxresp"));
        av++;

        for(; *av != NULL; av++)
                ctt_log(hp->vl, 0, "Unknown http rxresp spec: %s\n", *av);
        ctt_log(hp->vl, 3, "rxresp");
        http_rxhdr(hp);
        http_splitheader(hp, 0);
        if (!strcmp(hp->resp[1], "200"))
                http_swallow_body(hp, hp->resp, 1);
        else
                http_swallow_body(hp, hp->resp, 0);
        ctt_log(hp->vl, 4, "bodylen = %s", hp->bodylen);
        return (0);
}

static char *
cmd_var_resolve(struct http *hp, char *spec)
{
        char **hh, *hdr;

        if (!strcmp(spec, "req.request"))
                return (hp->req[0]);
        if (!strcmp(spec, "req.url"))
                return (hp->req[1]);
        if (!strcmp(spec, "req.proto"))
                return (hp->req[2]);
        if (!strcmp(spec, "req.readbytes")) {
                snprintf(hp->readbytes_len, sizeof(hp->readbytes_len), "%ld",
                    hp->readbytes);
                return (hp->readbytes_len);
        }
        if (!strcmp(spec, "req.writebytes")) {
                snprintf(hp->writebytes_len, sizeof(hp->writebytes_len), "%ld",
                    hp->writebytes);
                return (hp->writebytes_len);
        }
        if (!strcmp(spec, "resp.proto"))
                return (hp->resp[0]);
        if (!strcmp(spec, "resp.status"))
                return (hp->resp[1]);
        if (!strcmp(spec, "resp.msg"))
                return (hp->resp[2]);
        if (!strcmp(spec, "resp.bodylen"))
                return (hp->bodylen);
        if (!strcmp(spec, "resp.readbytes")) {
                snprintf(hp->readbytes_len, sizeof(hp->readbytes_len), "%ld",
                    hp->readbytes);
                return (hp->readbytes_len);
        }
        if (!strcmp(spec, "resp.writebytes")) {
                snprintf(hp->writebytes_len, sizeof(hp->writebytes_len), "%ld",
                    hp->writebytes);
                return (hp->writebytes_len);
        }
        if (!memcmp(spec, "req.http.", 9)) {
                hh = hp->req;
                hdr = spec + 9;
        } else if (!memcmp(spec, "resp.http.", 10)) {
                hh = hp->resp;
                hdr = spec + 10;
        } else
                return (spec);
        hdr = http_find_header(hh, hdr);
        if (hdr != NULL)
                return (hdr);
        return (strdup("undef"));
}

static int
cmd_http_expect(CMD_ARGS)
{
        struct http *hp;
        char *lhs;
        char *cmp;
        char *rhs;

        (void)cmd;
        (void)vl;
        CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
        assert(!strcmp(av[0], "expect"));
        av++;

        AN(av[0]);
        AN(av[1]);
        AN(av[2]);
        AZ(av[3]);
        lhs = cmd_var_resolve(hp, av[0]);
        cmp = av[1];
        rhs = cmd_var_resolve(hp, av[2]);
        if (!strcmp(cmp, "==")) {
                if (strcmp(lhs, rhs))
                        ctt_log(hp->vl, 0, "EXPECT %s (%s) %s %s (%s) failed",
                            av[0], lhs, av[1], av[2], rhs);
                else
                        ctt_log(hp->vl, 4, "EXPECT %s (%s) %s %s (%s) match",
                            av[0], lhs, av[1], av[2], rhs);
        } else if (!strcmp(cmp, "!=")) {
                if (!strcmp(lhs, rhs))
                        ctt_log(hp->vl, 0, "EXPECT %s (%s) %s %s (%s) failed",
                            av[0], lhs, av[1], av[2], rhs);
                else
                        ctt_log(hp->vl, 4, "EXPECT %s (%s) %s %s (%s) match",
                            av[0], lhs, av[1], av[2], rhs);
        } else {
                ctt_log(hp->vl, 0, "EXPECT %s (%s) %s %s (%s) not implemented",
                    av[0], lhs, av[1], av[2], rhs);
        }
        return (0);
}

static int
cmd_http_send(CMD_ARGS)
{
        struct http *hp;
        int i;

        (void)cmd;
        (void)vl;
        CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
        AN(av[1]);
        AZ(av[2]);
        ctt_dump(hp->vl, 4, "send", av[1]);
	i = write(hp->fd, av[1], strlen(av[1]));
	hp->writebytes += i;
        assert(i == strlen(av[1]));
        return (0);
}

static int
cmd_http_sendfile(CMD_ARGS)
{
#define MAXBUF  (64 * 1024)
        struct http *hp;
        FILE *fp;
        size_t r;
        int i;
        char buf[MAXBUF];

        (void)cmd;
        (void)vl;
        CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
        AN(av[1]);
        AZ(av[2]);
        ctt_dump(hp->vl, 4, "sendfile", av[1]);

        fp = fopen(av[1], "r");
        if (fp == NULL) {
                ctt_log(vl, 0, "could not find file %s", av[1]);
                return (0);
        }
        while ((r = fread(buf, 1, MAXBUF, fp)) != 0) {
		i = write(hp->fd, buf, r);
		hp->writebytes += i;
                assert(i == r);
        }
        assert(feof(fp) == 1);
        fclose(fp);
        return (0);
#undef MAXBUF
}

static void
http_process(struct cttlog *vl, const char *spec, int sock, int sfd)
{
        struct http *hp;
        char *s, *q;

        ALLOC_OBJ(hp, HTTP_MAGIC);
        AN(hp);
        hp->fd = sock;
        hp->timeout = ctt_timeout * 1000;
        hp->nrxbuf = 640 * 1024;
        hp->vsb = vsb_newauto();
        hp->rxbuf = malloc(hp->nrxbuf);         /* XXX */
        hp->sfd = sfd;
        hp->vl = vl;
        AN(hp->rxbuf);
        AN(hp->vsb);

        s = strdup(spec);
        q = strchr(s, '\0');
        assert(q > s);
        AN(s);
        parse_string(s, http_cmds, hp, vl);
        ctt_log(vl, 3, "traffic: read %ld write %ld", hp->readbytes,
            hp->writebytes);
        vsb_delete(hp->vsb);
        free(hp->rxbuf);
        free(hp);
}

/*
 * When closing a TCP connection, a couple of errno's are legit, we
 * can't be held responsible for the other end wanting to talk to us.
 */
static void
TCP_close(int *s)
{
        assert (close(*s) == 0 ||
            errno == ECONNRESET ||
            errno == ENOTCONN);
        *s = -1;
}

static void *
client_thread(void *priv)
{
        struct client *c;
        struct cttlog *vl;
        int fd = -1;
        int i;
        unsigned u;
        struct vsb *vsb;
        char *p;

        CAST_OBJ_NOTNULL(c, priv, CLIENT_MAGIC);
        AN(c->connect);

        vl = ctt_logopen(c->name);

        p = strdup(c->connect);
        vsb = macro_expand(vl, p);
        AN(vsb);

        if (c->repeat == 0)
                c->repeat = 1;
        ctt_log(vl, 2, "Started (%u iterations)", c->repeat);
        for (u = 0; u < c->repeat; u++) {
                ctt_log(vl, 3, "Connect to %s", vsb_data(vsb));
                fd = VSS_open(vsb_data(vsb));
                for (i = 0; fd < 0 && i < 3; i++) {
                        (void)sleep(1);
                        fd = VSS_open(vsb_data(vsb));
                }
                if (fd < 0)
                        ctt_log(c->vl, 0, "Failed to open %s", vsb_data(vsb));
                assert(fd >= 0);
                ctt_log(vl, 3, "Connected to %s fd is %d", vsb_data(vsb), fd);
                http_process(vl, c->spec, fd, -1);
                ctt_log(vl, 3, "Closing fd %d", fd);
                TCP_close(&fd);
        }
        ctt_log(vl, 2, "Ending");
        vsb_delete(vsb);
        free(p);
        return (NULL);
}

static void
client_start(struct client *c)
{

        CHECK_OBJ_NOTNULL(c, CLIENT_MAGIC);
        ctt_log(c->vl, 2, "Starting client");
        AZ(pthread_create(&c->tp, NULL, client_thread, c));
}

static void
client_wait(struct client *c)
{
        void *res;

        CHECK_OBJ_NOTNULL(c, CLIENT_MAGIC);
        ctt_log(c->vl, 2, "Waiting for client");
        AZ(pthread_join(c->tp, &res));
        if (res != NULL)
                ctt_log(c->vl, 0, "Client returned \"%s\"", (char *)res);
        c->tp = 0;
}

static void
client_run(struct client *c)
{

        client_start(c);
        client_wait(c);
}

static int
cmd_client(CMD_ARGS)
{
	struct client *c, *c2;

        (void)priv;
        (void)cmd;
        (void)vl;

        if (av == NULL) {
                /* Reset and free */
                VTAILQ_FOREACH_SAFE(c, &clients, list, c2) {
                        VTAILQ_REMOVE(&clients, c, list);
                        if (c->tp != 0)
                                client_wait(c);
                        client_delete(c);
                }
                return (0);
        }

        assert(!strcmp(av[0], "client"));
        av++;

        VTAILQ_FOREACH(c, &clients, list)
                if (!strcmp(c->name, av[0]))
                        break;
        if (c == NULL)
                c = client_new(av[0]);
        av++;

        for (; *av != NULL; av++) {
                if (ctt_error)
                        break;
                if (!strcmp(*av, "-connect")) {
                        REPLACE(c->connect, av[1]);
                        av++;
                        continue;
                }
                if (!strcmp(*av, "-repeat")) {
                        c->repeat = atoi(av[1]);
                        av++;
                        continue;
                }
                if (!strcmp(*av, "-start")) {
                        client_start(c);
                        continue;
                }
                if (!strcmp(*av, "-wait")) {
                        client_wait(c);
                        continue;
                }
                if (!strcmp(*av, "-run")) {
                        client_run(c);
                        continue;
                }
                if (**av == '-')
                        ctt_log(c->vl, 0, "Unknown client argument: %s", *av);
                REPLACE(c->spec, *av);
        }
        return (0);
}

static struct compile *
compile_new(const char *name)
{
        struct compile *c;

        AN(name);
        ALLOC_OBJ(c, COMPILE_MAGIC);
        AN(c);
        REPLACE(c->name, name);
        c->vl = ctt_logopen(name);
        AN(c->vl);
        if (*c->name != 'p')
                ctt_log(c->vl, 0, "Client name must start with 'p'");

        VTAILQ_INSERT_TAIL(&compiles, c, list);
        return (c);
}

static void
compile_delete(struct compile *c)
{

        CHECK_OBJ_NOTNULL(c, COMPILE_MAGIC);
        ctt_logclose(c->vl);
        free(c->name);
        /* xxx: memleak (?)*/
        FREE_OBJ(c);
}

static char *
compile_filename(const char *str)
{
	int i, len;
	static char fn[BUFSIZ];

	len = strlen(str);
	for (i = 0; i < len; i++) {
		if (str[i] == '.')
			break;
		fn[i] = str[i];
	}
	fn[i] = '\0';
	return (fn);
}

static void
compile_makefile(struct compile *c)
{
	struct vsb *vsb;
	char *temp, *cwd;
	char fn[BUFSIZ];
        ssize_t s;
        int fd, ret;

	temp = read_file("./tmpl/Makefile");
	if (temp == NULL) {
		ctt_log(c->vl, 0, "failed to read Makefile");
		return;
	}
	cwd = getcwd(fn, BUFSIZ);
        macro_def(c->vl, NULL, "filename", compile_filename(c->filename));
        macro_def(c->vl, NULL, "cwd", cwd);
	vsb = macro_expand(c->vl, temp);
	free(temp);
	sprintf(fn, "%s/build", macro_get("tmpdir"));
	ret = mkdir(fn, 0755);
	assert(ret == 0 || (ret == -1 && errno == EEXIST));
	sprintf(fn, "%s/build/Makefile", macro_get("tmpdir"));
	fd = open(fn, O_CREAT | O_WRONLY, 0644);
        assert(fd >= 0);
        s = write(fd, vsb_data(vsb), vsb_len(vsb));
	assert(s == vsb_len(vsb));
        AZ(close (fd));
	vsb_delete(vsb);
        macro_def(c->vl, NULL, "filename", NULL);
        macro_def(c->vl, NULL, "cwd", NULL);

	/* Copy the source */
	sprintf(fn, "tests/%s", c->filename);
	temp = read_file(fn);
	if (temp == NULL) {
		ctt_log(c->vl, 0, "failed to read %s", fn);
		return;
	}
	sprintf(fn, "%s/build/%s", macro_get("tmpdir"), c->filename);
	fd = open(fn, O_CREAT | O_WRONLY);
        assert(fd >= 0);
        s = write(fd, temp, strlen(temp));
	assert(s == strlen(temp));
        AZ(close (fd));
}


static void *
compile_thread(void *priv)
{
        struct compile *c;
        struct pollfd *fds, fd;
        char buf[BUFSIZ];
        int i;

        CAST_OBJ_NOTNULL(c, priv, COMPILE_MAGIC);
        TCP_nonblocking(c->fds[0]);
        while (1) {
                fds = &fd;
                memset(fds, 0, sizeof fds);
                fds->fd = c->fds[0];
                fds->events = POLLIN;
                i = poll(fds, 1, 1000);
                if (i == 0)
                        continue;
                if (fds->revents & (POLLERR|POLLHUP))
                        break;
                i = read(c->fds[0], buf, sizeof(buf) - 1);
                if (i <= 0)
                        break;
                buf[i] = '\0';
                ctt_dump(c->vl, 3, "debug", buf);
        }
        return (NULL);
}


static void
compile_run(struct compile *c)
{
        struct vsb *vsb;
        int i;

	compile_makefile(c);

        ctt_log(c->vl, 2, "Compile");
        vsb = vsb_newauto();
        AN(vsb);
	vsb_printf(vsb, " cd %s/build &&", macro_get("tmpdir"));
	vsb_printf(vsb, " make");
        vsb_finish(vsb);
        AZ(vsb_overflowed(vsb));
        ctt_log(c->vl, 3, "CMD: %s", vsb_data(vsb));
        AZ(pipe(&c->fds[0]));
        AZ(pipe(&c->fds[2]));
        c->pid = fork();
        assert(c->pid >= 0);
        if (c->pid == 0) {
                assert(dup2(c->fds[0], 0) == 0);
                assert(dup2(c->fds[3], 1) == 1);
                assert(dup2(1, 2) == 2);
                AZ(close(c->fds[0]));
                AZ(close(c->fds[1]));
                AZ(close(c->fds[2]));
                AZ(close(c->fds[3]));
                for (i = 3; i <getdtablesize(); i++)
                        close(i);
                AZ(execl("/bin/sh", "/bin/sh", "-c", vsb_data(vsb), NULL));
                exit(1);
        }
        AZ(close(c->fds[0]));
        AZ(close(c->fds[3]));
        c->fds[0] = c->fds[2];
        c->fds[2] = c->fds[3] = -1;
        vsb_delete(vsb);
        AZ(pthread_create(&c->tp, NULL, compile_thread, c));
        c->started = 1;
}

static void
compile_wait(struct compile *c)
{
        void *p;
        int status, r;

        if (c->started == 0)
                return;
        if (ctt_error)
                sleep(1);
        ctt_log(c->vl, 2, "Wait");
        c->started = 0;

        AZ(close(c->fds[1]));

        AZ(pthread_join(c->tp, &p));
        AZ(close(c->fds[0]));
        status = -1;
        r = wait4(c->pid, &status, 0, NULL);
        ctt_log(c->vl, 2, "R %d Status: %04x", r, status);
        if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
                return;
        ctt_log(c->vl, 0, "Bad exit code: %04x sig %x exit %x core %x",
            status, WTERMSIG(status), WEXITSTATUS(status),
            WCOREDUMP(status));
}


static int
cmd_compile(CMD_ARGS)
{
	struct compile *c, *c2;

        (void)priv;
        (void)cmd;
        (void)vl;

        if (av == NULL) {
                /* Reset and free */
                VTAILQ_FOREACH_SAFE(c, &compiles, list, c2) {
			if (c->started == 1)
				compile_wait(c);
                        VTAILQ_REMOVE(&compiles, c, list);
                        compile_delete(c);
                }
                return (0);
        }

        assert(!strcmp(av[0], "compile"));
        av++;

        VTAILQ_FOREACH(c, &compiles, list)
                if (!strcmp(c->name, av[0]))
                        break;
        if (c == NULL)
                c = compile_new(av[0]);
        av++;

        for (; *av != NULL; av++) {
                if (ctt_error)
                        break;
                if (!strcmp(*av, "-filename")) {
			AN(av[1]);
                        c->filename = av[1];
                        av++;
                        compile_run(c);
                        continue;
                }
		if (!strcmp(*av, "-wait")) {
			compile_wait(c);
                        continue;
                }
                if (**av == '-')
                        ctt_log(c->vl, 0, "Unknown compile argument: %s", *av);
        }
        return (0);
}


static int
cmd_delay(CMD_ARGS)
{
	double f;

        (void)priv;
        (void)cmd;
        if (av == NULL)
                return (0);
        AN(av[1]);
        AZ(av[2]);
        f = strtod(av[1], NULL);
        ctt_log(vl, 3, "delaying %g second(s)", f);
        TIM_sleep(f);
        return (0);
}

static int
cmd_shell(CMD_ARGS)
{
        int status = 1, expectedstatus = 0, expectexit;
        char *cmds = NULL;

        (void)priv;
        (void)cmd;

        if (av == NULL)
                return (0);
        assert(!strcmp(av[0], "shell"));
        av++;

        expectexit = 0;
        for (; *av != NULL; av++) {
                if (ctt_error)
                        break;
                ctt_dump(vl, 4, "shell", *av);
                if (!strcmp(*av, "-expect")) {
                        expectedstatus = atoi(av[1]);
                        av++;
                        ctt_dump(vl, 4, "shell", *av);
                        continue;
                } else if (!strcmp(*av, "-expectexit")) {
                        expectexit++;
                        expectedstatus = atoi(av[1]);
                        av++;
                        ctt_dump(vl, 4, "shell", *av);
                        continue;
                } else if (!strcmp(*av, "-cmd")) {
                        cmds = av[1];
                        status = WEXITSTATUS(system(cmds));
                        av++;
                        ctt_dump(vl, 4, "shell", *av);
                        continue;
                }
                if (**av == '-')
                        ctt_log(vl, 0, "Unknown client argument: %s", *av);
        }

        if (status != expectedstatus)
                ctt_log(vl, expectexit > 0 ? 1 : 0,
                    "FAIL shell command `%s' (status %d expected %d)",
                    cmds, status, expectedstatus);
        if (expectexit > 0 && status != expectedstatus)
                return (-1);
        return (0);
}

static int
cmd_test(CMD_ARGS)
{

        (void)priv;
        (void)cmd;
        (void)vl;

        if (av == NULL)
                return (0);
        assert(!strcmp(av[0], "test"));

        ctt_log(vl, 1, "#    TEST %s", av[1]);
        AZ(av[2]);
        ctt_desc = strdup(av[1]);
        return (0);
}

static struct sema *
sema_new(char *name, struct cttlog *vl)
{
        struct sema *r;

        ALLOC_OBJ(r, SEMA_MAGIC);
        AN(r);
        r->name = name;
        if (*name != 'r')
                ctt_log(vl, 0, "Sema name must start with 'r' (%s)", name);

        AZ(pthread_mutex_init(&r->mtx, NULL));
        AZ(pthread_cond_init(&r->cond, NULL));
        r->waiters = 0;
        r->expected = 0;
        VTAILQ_INSERT_TAIL(&semas, r, list);
        return (r);
}

static void
sema_sync(struct sema *r, const char *av, struct cttlog *vl)
{
        unsigned u;

        CHECK_OBJ_NOTNULL(r, SEMA_MAGIC);
        u = strtoul(av, NULL, 0);

        AZ(pthread_mutex_lock(&r->mtx));
        if (r->expected == 0)
                r->expected = u;
        if (r->expected != u)
                ctt_log(vl, 0,
                    "Sema(%s) use error: different expectations (%u vs %u)",
                    r->name, r->expected, u);

        if (++r->waiters == r->expected) {
                ctt_log(vl, 4, "Sema(%s) wake %u", r->name, r->expected);
                AZ(pthread_cond_broadcast(&r->cond));
                r->waiters = 0;
                r->expected = 0;
        } else {
                ctt_log(vl, 4, "Sema(%s) wait %u of %u",
                    r->name, r->waiters, r->expected);
                AZ(pthread_cond_wait(&r->cond, &r->mtx));
        }
        AZ(pthread_mutex_unlock(&r->mtx));
}

static int
cmd_sema(CMD_ARGS)
{
        struct sema *r, *r2;

        (void)priv;
        (void)cmd;

        if (av == NULL) {
                AZ(pthread_mutex_lock(&sema_mtx));
                /* Reset and free */
                VTAILQ_FOREACH_SAFE(r, &semas, list, r2) {
                        AZ(pthread_mutex_lock(&r->mtx));
                        AZ(r->waiters);
                        AZ(r->expected);
                        AZ(pthread_mutex_unlock(&r->mtx));
                }
                AZ(pthread_mutex_unlock(&sema_mtx));
                return (0);
        }

        assert(!strcmp(av[0], "sema"));
        av++;

        AZ(pthread_mutex_lock(&sema_mtx));
        VTAILQ_FOREACH(r, &semas, list)
                if (!strcmp(r->name, av[0]))
                        break;
        if (r == NULL)
                r = sema_new(av[0], vl);
        AZ(pthread_mutex_unlock(&sema_mtx));
        av++;

        for (; *av != NULL; av++) {
                if (!strcmp(*av, "sync")) {
                        av++;
                        AN(*av);
                        sema_sync(r, *av, vl);
                        continue;
                }
                ctt_log(vl, 0, "Unknown sema argument: %s", *av);
        }
        return (0);
}

#define NRNDEXPECT      12
static const uint32_t random_expect[NRNDEXPECT] = {
        1804289383,     846930886,      1681692777,     1714636915,
        1957747793,     424238335,      719885386,      1649760492,
        596516649,      1189641421,     1025202362,     1350490027
};

#define RND_NEXT_1K     0x3bdcbe30

static int
cmd_random(CMD_ARGS)
{
        uint32_t l;
        int i;

        (void)cmd;
        (void)priv;
        if (av == NULL)
                return (0);
        srandom(1);
        for (i = 0; i < NRNDEXPECT; i++) {
                l = random();
                if (l == random_expect[i])
                        continue;
                ctt_log(vl, 4, "random[%d] = 0x%x (expect 0x%x)",
                    i, l, random_expect[i]);
                ctt_log(vl, 1, "SKIPPING test: unknown srandom(1) sequence.");
                ctt_stop = 1;
                break;
        }
        l = 0;
        for (i = 0; i < 1000; i++)
                l += random();
        if (l != RND_NEXT_1K) {
                ctt_log(vl, 4, "sum(random[%d...%d]) = 0x%x (expect 0x%x)",
                    NRNDEXPECT, NRNDEXPECT + 1000,
                    l, RND_NEXT_1K);
                ctt_log(vl, 1, "SKIPPING test: unknown srandom(1) sequence.");
                ctt_stop = 1;
        }
        return (0);
}

static void
usage(void)
{

        fprintf(stderr,
            "usage: cfptest [-n iter] [-s apache|lighttpd] [-t timeout]"
	    " [-qv] file ...\n");
        exit(1);
}

int
main(int argc, char * const *argv)
{
	static struct cttlog *cl;
	struct sigaction sa;
	FILE *fok;
	double tmax, t0, t00;
	int ch, i, ncheck = 0, ntest = 1;
	const char *nmax;
	char cmd[BUFSIZ];
	char *tmpdir;

	setbuf(stdout, NULL);
        setbuf(stderr, NULL);
	ctt_loginit();
	cl = ctt_logopen("top");
	AN(cl);
	while ((ch = getopt(argc, argv, "n:qs:t:v")) != -1) {
                switch (ch) {
                case 'n':
                        ntest = strtoul(optarg, NULL, 0);
                        break;
                case 'q':
                        ctt_verbosity--;
                        break;
		case 's':
			if (!strcmp(optarg, "apache"))
				ctt_server = CTT_SERVER_APACHE;
			if (!strcmp(optarg, "lighttpd"))
				ctt_server = CTT_SERVER_LIGHTTPD;
			break;
                case 't':
                        ctt_timeout = (int)strtoul(optarg, NULL, 0);
                        break;
                case 'v':
                        ctt_verbosity++;
                        break;
                default:
                        usage();
                }
	}
	argc -= optind;
        argv += optind;

        if (argc == 0)
                usage();

	init_macro();
	init_sema();

	ctt_tmpdir = NULL;
	tmpdir = getenv("TMPDIR");
	if (tmpdir == NULL)
                asprintf(&ctt_tmpdir, "%s%s.XXXXXXXX", _PATH_TMP, "ctt");
        else {
                tmpdir = strdup(tmpdir);
                AN(tmpdir);
                if (tmpdir[strlen(tmpdir) - 1] == '/')
                        tmpdir[strlen(tmpdir) - 1] = '\0';
                asprintf(&ctt_tmpdir, "%s/%s.XXXXXXXX", tmpdir, "ctt");
        }
        AN(ctt_tmpdir);
        if (mkdtemp(ctt_tmpdir) == NULL) {
                printf("mkdtemp failed on %s\n", ctt_tmpdir);
                exit(1);
        }
        macro_def(cl, NULL, "tmpdir", ctt_tmpdir);
        /* Ignore SIGPIPE. */
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = SIG_IGN;
        sa.sa_flags = SA_RESTART;
        assert(sigaction(SIGPIPE, &sa, NULL) == 0);

	ctt_thread = pthread_self();
        tmax = 0;
        nmax = NULL;
        t00 = TIM_mono();
        for (i = 0; i < ntest; i++) {
                for (ch = 0; ch < argc; ch++) {
                        t0 = exec_file(argv[ch], ch + 1, argc, cl);
                        ncheck++;
                        if (t0 > tmax) {
                                tmax = t0;
                                nmax = argv[ch];
                        }
                        if (ctt_error)
                                break;
                }
                if (ctt_error)
                        break;
        }

	/* Remove tmpdir on success or non-verbosity */
        if (ctt_error == 0 || ctt_verbosity == 0) {
                bprintf(cmd, "rm -rf %s", ctt_tmpdir);
                AZ(system(cmd));
                free(ctt_tmpdir);
                ctt_tmpdir = NULL;
                free(tmpdir);
                tmpdir = NULL;
        }

        if (ctt_error)
                return (2);

        t00 = TIM_mono() - t00;
        if (ncheck > 1) {
                printf("#    top  Slowest test: %s %.3fs\n", nmax, tmax);
                printf("#    top  Total tests run:   %d\n", ncheck);
                printf("#    top  Total duration: %.3fs\n", t00);
        }

	fok = fopen("_.ok", "w");
        if (fok != NULL)
                fclose(fok);
        free(ctt_tmpdir);
        free(tmpdir);
	return (0);
}
